<?php
/**
 * Copyright (c) 2006-2008, Julien PORTALIER
 * http://featherphp.googlecode.com/
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 */

/**
 * Templates system.
 */
class View /*extends BaseCallback*/
{
	# vars
	protected $view_vars        = array();
	protected $view_vars_layout = array();
	
	# paths
	public    $base_path;
	public    $view_path;
	public    $view_format      = '';
	protected $templates        = ''; 
	public    $templates_path;
	
	# callbacks
#	protected $before_render    = array();
#	protected $after_render     = array();
	
#	function ignite_view()
	function __construct()
	{
		$plugin = empty(Core::$plugin) ? '' : 'plugins'.DS.Core::$plugin.DS;
		$this->view_path = Inflector::underscore($this->name);
		$this->base_path = APP.$plugin.'views'.DS;
	}
	
	/**
	 * Passes a variable to view.
	 * 
	 * Examples:
	 * 
	 * $this->set('variable', $value);
	 * $this->set(array('var1' => $val1, 'var2' => $val2));
	 * 
	 * @param $var   mixed
	 * @param $value mixed[optional]
	 */
	function set($var, $value=null)
	{
		if (!is_array($var)) {
			$this->view_vars[$var] =& $value;
		}
		else {
			$this->view_vars = array_merge($this->view_vars, $var);
		}
	}
	
	/**
	 * Returns an assigned variable.
	 * 
	 * @return mixed
	 * @param $var String
	 */
	function get($var)
	{
		return isset($this->view_vars[$var]) ? $this->view_vars[$var] : null;
	}
	
	/**
	 * Assigns a variable for the layout only.
	 * 
	 * Works just like View::set().
	 * 
	 * @param $var   mixed
	 * @param $value mixed[optional]
	 */
	function set_for_layout($var, $value=null)
	{
		if (!is_array($var)) {
			$this->view_vars_layout[$var] =& $value;
		}
		else {
			$this->view_vars_layout = array_merge($this->view_vars_layout, $var);
		}
	}
	
	/**
	 * Assigns a variable for the layout only.
	 * 
	 * It works just like View::get().
	 * 
	 * @return mixed
	 * @param $var mixed
	 */
	function get_for_layout($var)
	{
		return isset($this->view_vars_layout[$var]) ? $this->view_vars_layout[$var] : null;
	}
	
	
	/*
	function view_exists($view)
	{
		$view = "$view.{$this->view_format}";
		return (file_exists($this->base_path.$this->view_path.DS."$view.tpl")
			or file_exists(FEATHER.'views'.DS.'templates'.DS.$this->view_path.DS."$view.tpl"));
	}
	*/
	
	/**
	 * Renders a view in a layout, and echoes it.
	 * 
	 * @param $action String
	 * @param $layout String
	 * @param $format String[optional]
	 */
	function render($action, $layout, $format=null)
	{
		# we cannot access $this outside the object scope,
		# and we cannot define superglobals vars, we thus
		# have to use $GLOBALS to store $this :(
		$GLOBALS['self'] = $this;
		
		# helpers
		$helpers = array_merge(Core::$helpers, $this->helpers);
		Load::helpers(&$helpers);
		
		if (!empty($action)) {
			$action = $this->view_path.DS.$action;
		}
		$this->view_format = $format;
		
		# view + layout
#		$this->callback('before_render');
		$this->before_render();
		$render = $this->_render($action, $layout);
#		$this->callback('after_render', &$render);
		$this->after_render(&$render);
		
		# display!
		echo $render;
	}
	
	
	// elements
	
	/**
	 * Checks if an element exists.
	 * 
	 * @return Boolean
	 * @param $element String
	 */
	function element_exists($element)
	{
		$element = "$element.{$this->view_format}";
		return (file_exists($this->base_path.'elements'.DS."$element.tpl")
			or file_exists(FEATHER.'views'.DS.'elements'.DS."$element.tpl"));
	}
	
	/**
	 * Renders an element.
	 * 
	 * @return String
	 * @param $element String
	 * @param $views_vars[optional] Array
	 */
	function render_element($element, array $view_vars=null)
	{
		return $this->_render('elements'.DS.$element, null, $view_vars, false);
	}
	
	
	/**
	 * Checks if a partial exists.
	 * 
	 * @return Boolean
	 * @param $partial String
	 */
	function partial_exists($partial)
	{
		$partial = "$partial.{$this->view_format}";
		return (file_exists($this->base_path.$this->view_path.DS."_$partial.tpl")
			or file_exists(FEATHER.'views'.DS.$this->view_path.DS."_$partial.tpl"));
	}
	
	/**
	 * Renders a partial.
	 * 
	 * @return String
	 * @param $partial String
	 * @param $views_vars[optional] Array
	 */
	function render_partial($partial, $view_vars=null, $controller=null)
	{
		$_partial = empty($controller) ? $this->view_path.DS."_$partial" : $controller.DS."_$partial";
		return $this->_render($_partial, null, $view_vars, true);
#		return $this->_render($this->view_path.DS."_$partial", null, $view_vars, true);
	}
	
	
	private function _render($_file, $_layout=null, array $_view_vars=null, $_template=true)
	{
		# assigned vars
		foreach(array_keys($this->view_vars) as $_view_var) {
			$$_view_var =& $this->view_vars[$_view_var];
		}
		
		# more assigned vars
		if (!empty($_view_vars))
		{
			foreach(array_keys($_view_vars) as $_view_var) {
				$$_view_var =& $_view_vars[$_view_var];
			}
		}
		
		# layout's vars
		foreach(array_keys($this->view_vars_layout) as $_view_var)
		{
			$_view_var2  = "{$_view_var}_for_layout";
			$$_view_var2 =& $this->view_vars_layout[$_view_var];
		}
		unset($_view_var);
		unset($_view_var2);
		
		# renders a view, template or partial
		if (!empty($_file))
		{
			ob_start();
			
			$_file = "{$_file}.{$this->view_format}";
			if ($_template and !empty($this->templates)) {
				$_template_file = preg_replace(':^[^'.DS.']+'.DS.':', $this->templates.DS, $_file);
			}
			
			# views/$view/$file.$format.tpl
			if ($this->_tpl_file_exists("views".DS."{$_file}.tpl")) {
				require "views".DS."{$_file}.tpl";
			}
			
			# views/templates/$template/$file.$format.tpl
			elseif (isset($_template_file)
				and $this->_tpl_file_exists("views".DS."templates".DS."{$_template_file}.tpl"))
			{
				require "views".DS."templates".DS."{$_template_file}.tpl";
			}
			
			# views/templates/default.$format.tpl
			elseif ($this->_tpl_file_exists('views'.DS.'templates'.DS."default.{$this->view_format}.tpl")) {
				require 'views'.DS.'templates'.DS."default.{$this->view_format}.tpl";
			}
			
			$content_for_layout = ob_get_clean();
		}
		elseif (!isset($content_for_layout)) {
			$content_for_layout = '';
		}
		
		# renders layout
		if ($_layout !== null)
		{
			# layout's vars (a view may have assigned more vars for the layout)
			foreach(array_keys($this->view_vars_layout) as $_view_var)
			{
				$_view_var2  = "{$_view_var}_for_layout";
				$$_view_var2 =& $this->view_vars_layout[$_view_var];
			}
			unset($_view_var);
			unset($_view_var2);
			
			ob_start();
			$_layout = "{$_layout}.{$this->view_format}";
			
			# APP: views/layouts/layout.format.tpl
			if (file_exists(APP.'views'.DS.'layouts'.DS."{$_layout}.tpl")) {
				require APP.'views'.DS.'layouts'.DS."{$_layout}.tpl";
			}
			
			# FEATHER: views/templates/layouts/layout.format.tpl
			elseif (file_exists(FEATHER.'views'.DS.'templates'.DS.'layouts'.DS."{$_layout}.tpl")) {
				require FEATHER.'views'.DS.'templates'.DS.'layouts'.DS."{$_layout}.tpl";
			}
			else {
				throw new Exception("Unable to find layout '{$_layout}.tpl'.", 404);
			}
			
			return ob_get_clean();
		}
		
		return $content_for_layout;
	}
	
	
	private function _tpl_file_exists($tpl)
	{
		return (
			(isset(Core::$plugin) and file_exists(APP.'plugins'.DS.Core::$plugin.DS.$tpl))
			or file_exists(APP.$tpl)
			or file_exists(FEATHER.$tpl)
		);
	}
	
	
	/**
	 * Displays a transitional message.
	 * For instance a success message after sending an email.
	 * 
	 * @param $title   String Title of message to display.
	 * @param $message String Message to display. 
	 */
	function message($title, $message=null)
	{
		$this->set(array(
			'title'   => $title,
			'message' => $message
		));
		$this->templates = 'message';
		$this->render('message');
		exit;
	}
	
	/**
	 * Always called before rendering a view.
	 */
	protected function before_render() {}
	
	/**
	 * Always called after a view has been rendered.
	 */
	protected function after_render(&$render)  {}
}
?>